/*
 * Copyright (c) 2012-2017 The Khronos Group Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "test_engine/test.h"
#include <VX/vx.h>
#include <VX/vxu.h>

#include <math.h>
#include <float.h>

TESTCASE(Remap, CT_VXContext, ct_setup_vx_context, 0)

TEST(Remap, testNodeCreation)
{
    vx_uint32 i;
    vx_uint32 j;
    vx_context context = context_->vx_context_;
    vx_image input = 0, output = 0;
    vx_uint32 src_width;
    vx_uint32 src_height;
    vx_uint32 dst_width;
    vx_uint32 dst_height;
    vx_remap map = 0;
    vx_enum attr_name[] =
    {
        VX_REMAP_SOURCE_WIDTH,
        VX_REMAP_SOURCE_HEIGHT,
        VX_REMAP_DESTINATION_WIDTH,
        VX_REMAP_DESTINATION_HEIGHT
    };
    vx_uint32 attr_val[4];
    vx_graph graph = 0;
    vx_node node = 0;
    vx_enum interp = VX_INTERPOLATION_NEAREST_NEIGHBOR;

    src_width = 16;
    src_height = 32;
    dst_width = 128;
    dst_height = 64;

    ASSERT_VX_OBJECT(input = vxCreateImage(context, src_width, src_height, VX_DF_IMAGE_U8), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(output = vxCreateImage(context, dst_width, dst_height, VX_DF_IMAGE_U8), VX_TYPE_IMAGE);

    map = vxCreateRemap(context, src_width, src_height, dst_width, dst_height);
    ASSERT_VX_OBJECT(map, VX_TYPE_REMAP);

    vx_rectangle_t rect = { 0, 0, dst_width, dst_height};
    vx_size stride = dst_width;
    vx_size stride_y = sizeof(vx_coordinates2df_t) * (stride);
    vx_size size = stride * dst_height;
    vx_coordinates2df_t *ptr_w = ct_calloc(size, sizeof(vx_coordinates2df_t));
    ASSERT(ptr_w);

    for (i = 0; i < dst_height; i++)
    {
        for (j = 0; j < dst_width; j++)
        {
            vx_coordinates2df_t *coord_ptr = &(ptr_w[i * stride + j]);
            coord_ptr->x = (vx_float32)j;
            coord_ptr->y = (vx_float32)i;
        }
    }

    VX_CALL(vxCopyRemapPatch(map, &rect, stride_y, ptr_w, VX_TYPE_COORDINATES2DF, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST));
    ct_free_mem(ptr_w);

    graph = vxCreateGraph(context);
    ASSERT_VX_OBJECT(graph, VX_TYPE_GRAPH);

    node = vxRemapNode(graph, input, map, interp, output);
    ASSERT_VX_OBJECT(node, VX_TYPE_NODE);
    VX_CALL(vxQueryRemap(map, attr_name[0], &attr_val[0], sizeof(attr_val[0])));
    if (attr_val[0] != src_width)
    {
        CT_FAIL("check for remap attribute VX_REMAP_SOURCE_WIDTH failed\n");
    }
    VX_CALL(vxQueryRemap(map, attr_name[1], &attr_val[1], sizeof(attr_val[1])));
    if (attr_val[1] != src_height)
    {
        CT_FAIL("check for remap attribute VX_REMAP_SOURCE_HEIGHT failed\n");
    }
    VX_CALL(vxQueryRemap(map, attr_name[2], &attr_val[2], sizeof(attr_val[2])));
    if (attr_val[2] != dst_width)
    {
        CT_FAIL("check for remap attribute VX_REMAP_DESTINATION_WIDTH failed\n");
    }
    VX_CALL(vxQueryRemap(map, attr_name[3], &attr_val[3], sizeof(attr_val[3])));
    if (attr_val[3] != dst_height)
    {
        CT_FAIL("check for remap attribute VX_REMAP_DESTINATION_HEIGHT failed\n");
    }

    VX_CALL(vxReleaseNode(&node));
    VX_CALL(vxReleaseGraph(&graph));
    VX_CALL(vxReleaseImage(&output));
    VX_CALL(vxReleaseRemap(&map));
    VX_CALL(vxReleaseImage(&input));

    ASSERT(node == 0);
    ASSERT(graph == 0);
    ASSERT(output == 0);
    ASSERT(map == 0);
    ASSERT(input == 0);
}

#define SRC_WIDTH       128
#define SRC_HEIGHT      128

#define VX_MAP_IDENT         0
#define VX_MAP_SCALE         1
#define VX_MAP_SCALE_ROTATE  2
#define VX_MAP_RANDOM        3

#define VX_NN_AREA_SIZE         1.5
#define VX_BILINEAR_TOLERANCE   1

static CT_Image remap_read_image_8u(const char* fileName, int width, int height)
{
    CT_Image image = NULL;

    image = ct_read_image(fileName, 1);
    ASSERT_(return 0, image);
    ASSERT_(return 0, image->format == VX_DF_IMAGE_U8);

    return image;
}

static CT_Image remap_generate_random(const char* fileName, int width, int height)
{
    CT_Image image;

    ASSERT_NO_FAILURE_(return 0,
            image = ct_allocate_ct_image_random(width, height, VX_DF_IMAGE_U8, &CT()->seed_, 0, 256));

    return image;
}

#define RND_FLT(low, high)      (vx_float32)CT_RNG_NEXT_REAL(CT()->seed_, low, high);

static vx_remap remap_generate_map(vx_context context, int src_width, int src_height, int dst_width, int dst_height, int type)
{
    vx_uint32 i;
    vx_uint32 j;
    vx_float32 x;
    vx_float32 y;
    vx_remap map = 0;

    map = vxCreateRemap(context, src_width, src_height, dst_width, dst_height);
    if (vxGetStatus((vx_reference)map) == VX_SUCCESS)
    {
        vx_float32 mat[3][2];
        vx_float32 angle, scale_x, scale_y, cos_a, sin_a;
        if (VX_MAP_IDENT == type)
        {
            mat[0][0] = 1.f;
            mat[0][1] = 0.f;

            mat[1][0] = 0.f;
            mat[1][1] = 1.f;

            mat[2][0] = 0.f;
            mat[2][1] = 0.f;
        }
        else if (VX_MAP_SCALE == type)
        {
            scale_x = src_width  / (vx_float32)dst_width;
            scale_y = src_height / (vx_float32)dst_height;

            mat[0][0] = scale_x;
            mat[0][1] = 0.f;

            mat[1][0] = 0.f;
            mat[1][1] = scale_y;

            mat[2][0] = 0.f;
            mat[2][1] = 0.f;
        }
        else if (VX_MAP_SCALE_ROTATE == type)
        {
            angle = M_PIF / RND_FLT(3.f, 6.f);
            scale_x = src_width  / (vx_float32)dst_width;
            scale_y = src_height / (vx_float32)dst_height;
            cos_a = cosf(angle);
            sin_a = sinf(angle);

            mat[0][0] = cos_a * scale_x;
            mat[0][1] = sin_a * scale_y;

            mat[1][0] = -sin_a * scale_x;
            mat[1][1] = cos_a  * scale_y;

            mat[2][0] = 0.f;
            mat[2][1] = 0.f;
        }
        else// if (VX_MATRIX_RANDOM == type)
        {
            angle = M_PIF / RND_FLT(3.f, 6.f);
            scale_x = src_width / (vx_float32)dst_width;
            scale_y = src_height / (vx_float32)dst_height;
            cos_a = cosf(angle);
            sin_a = sinf(angle);

            mat[0][0] = cos_a * RND_FLT(scale_x / 2.f, scale_x);
            mat[0][1] = sin_a * RND_FLT(scale_y / 2.f, scale_y);

            mat[1][0] = -sin_a * RND_FLT(scale_y / 2.f, scale_y);
            mat[1][1] = cos_a  * RND_FLT(scale_x / 2.f, scale_x);

            mat[2][0] = src_width  / 5.f * RND_FLT(-1.f, 1.f);
            mat[2][1] = src_height / 5.f * RND_FLT(-1.f, 1.f);
        }


        vx_rectangle_t rect = { 0, 0, dst_width, dst_height};
        vx_size stride = dst_width;
        vx_size stride_y = sizeof(vx_coordinates2df_t) * (stride);
        vx_size size = stride * dst_height;
        vx_coordinates2df_t *ptr_w = ct_calloc(size, sizeof(vx_coordinates2df_t));
        ASSERT_(return 0, ptr_w);

        for (i = 0; i < (vx_uint32)dst_height; i++)
        {
            for (j = 0; j < (vx_uint32)dst_width; j++)
            {
                x = j * mat[0][0] + i * mat[1][0] + mat[2][0];
                y = j * mat[0][1] + i * mat[1][1] + mat[2][1];
                vx_coordinates2df_t *coord_ptr = &(ptr_w[i * stride + j]);
                coord_ptr->x = x;
                coord_ptr->y = y;
            }
        }

        vxCopyRemapPatch(map, &rect, stride_y, ptr_w, VX_TYPE_COORDINATES2DF, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST);    
        ct_free_mem(ptr_w);
    }

    return map;
}


static int remap_check_pixel(CT_Image input, CT_Image output, int x, int y, vx_float32 _x0,
     vx_float32 _y0, vx_enum interp_type, vx_border_t border, vx_remap map, vx_bool do_report)
{
    vx_float64 x0, y0, xlower, ylower, s, t;
    vx_int32 xi, yi;
    int candidate;
    vx_uint8 res = *CT_IMAGE_DATA_PTR_8U(output, x, y);

    x0 = (vx_float64)_x0;
    y0 = (vx_float64)_y0;

    if (VX_INTERPOLATION_NEAREST_NEIGHBOR == interp_type)
    {
        for (yi = (vx_int32)ceil(y0 - VX_NN_AREA_SIZE); (vx_float64)yi <= y0 + VX_NN_AREA_SIZE; yi++)
        {
            for (xi = (vx_int32)ceil(x0 - VX_NN_AREA_SIZE); (vx_float64)xi <= x0 + VX_NN_AREA_SIZE; xi++)
            {
                if (0 <= xi && 0 <= yi && xi < (vx_int32)input->width && yi < (vx_int32)input->height)
                {
                    candidate = *CT_IMAGE_DATA_PTR_8U(input, xi, yi);
                }
                else if (VX_BORDER_CONSTANT == border.mode)
                {
                    candidate = border.constant_value.U8;
                }
                else if (VX_BORDER_REPLICATE == border.mode)
                {
                    candidate = CT_IMAGE_DATA_REPLICATE_8U(input, xi, yi);
                }
                else
                {
                    candidate = -1;
                }
                if (candidate == -1 || candidate == res)
                    return 0;
            }
        }
        if (do_report)
            CT_FAIL_(return 1, "Check failed for pixel (%d, %d): %d", x, y, (int)res);
        else
            return 1;
    }
    else if (VX_INTERPOLATION_BILINEAR == interp_type)
    {
        xlower = floor(x0);
        ylower = floor(y0);

        s = x0 - xlower;
        t = y0 - ylower;

        xi = (vx_int32)xlower;
        yi = (vx_int32)ylower;

        candidate = -1;
        if (VX_BORDER_UNDEFINED == border.mode)
        {
            if (xi >= 0 && yi >= 0 && xi < (vx_int32)input->width - 1 && yi < (vx_int32)input->height - 1)
            {
                candidate = (int)((1. - s) * (1. - t) * (vx_float64) *CT_IMAGE_DATA_PTR_8U(input, xi    , yi    ) +
                                        s  * (1. - t) * (vx_float64) *CT_IMAGE_DATA_PTR_8U(input, xi + 1, yi    ) +
                                  (1. - s) *       t  * (vx_float64) *CT_IMAGE_DATA_PTR_8U(input, xi    , yi + 1) +
                                        s  *       t  * (vx_float64) *CT_IMAGE_DATA_PTR_8U(input, xi + 1, yi + 1));
            }
        }
        else if (VX_BORDER_CONSTANT == border.mode)
        {
            candidate = (int)((1. - s) * (1. - t) * (vx_float64)CT_IMAGE_DATA_CONSTANT_8U(input, xi    , yi    , border.constant_value.U8) +
                                    s  * (1. - t) * (vx_float64)CT_IMAGE_DATA_CONSTANT_8U(input, xi + 1, yi    , border.constant_value.U8) +
                              (1. - s) *       t  * (vx_float64)CT_IMAGE_DATA_CONSTANT_8U(input, xi    , yi + 1, border.constant_value.U8) +
                                    s  *       t  * (vx_float64)CT_IMAGE_DATA_CONSTANT_8U(input, xi + 1, yi + 1, border.constant_value.U8));
        }
        else if (VX_BORDER_REPLICATE == border.mode)
        {
            candidate = (int)((1. - s) * (1. - t) * (vx_float64)CT_IMAGE_DATA_REPLICATE_8U(input, xi    , yi    ) +
                                    s  * (1. - t) * (vx_float64)CT_IMAGE_DATA_REPLICATE_8U(input, xi + 1, yi    ) +
                              (1. - s) *       t  * (vx_float64)CT_IMAGE_DATA_REPLICATE_8U(input, xi    , yi + 1) +
                                    s  *       t  * (vx_float64)CT_IMAGE_DATA_REPLICATE_8U(input, xi + 1, yi + 1));
        }
        if (candidate == -1 || (abs(candidate - res) <= VX_BILINEAR_TOLERANCE))
            return 0;
        return 1;
    }
    if (do_report)
        CT_FAIL_(return 1, "Interpolation type undefined");
    else
        return 1;
}

static void remap_validate(CT_Image input, CT_Image output, vx_enum interp_type, vx_border_t border, vx_remap map)
{
    vx_uint32 err_count = 0;

    ASSERT(output != NULL);
    ASSERT(output->format == VX_DF_IMAGE_U8);
    ASSERT(output->width > 0);
    ASSERT(output->height > 0);
    {
        vx_rectangle_t rect = { 0, 0, output->width, output->height};
        vx_size stride = output->width;
        vx_size stride_y = sizeof(vx_coordinates2df_t) * (stride);
        vx_size size = stride * output->height;
        vx_coordinates2df_t *ptr_r = ct_calloc(size, sizeof(vx_coordinates2df_t));
        ASSERT(ptr_r);
        vxCopyRemapPatch(map, &rect, stride_y, ptr_r, VX_TYPE_COORDINATES2DF, VX_READ_ONLY, VX_MEMORY_TYPE_HOST);

        uint32_t x, y;
        for (y = 0; y < output->height; y++) {
            for (x = 0; x < output->width; x++) {
                uint8_t* dst_data = CT_IMAGE_DATA_PTR_8U(output, x, y); (void)dst_data;
                {
                    vx_coordinates2df_t *coord_ptr = &(ptr_r[y * stride + x]);
                    ASSERT_NO_FAILURE(err_count += remap_check_pixel(input, output, x, y, coord_ptr->x, coord_ptr->y,
                                                                     interp_type, border, map, vx_true_e));
                }
            }
        }
        ct_free_mem(ptr_r);
    }

    if (10 * err_count > output->width * output->height)
        CT_FAIL_(return, "Check failed for %d pixels", err_count);
}

static vx_bool remap_is_equal(CT_Image input, CT_Image output, vx_enum interp_type, vx_border_t border, vx_remap map)
{
    if( (output != NULL) &&
        (output->format == VX_DF_IMAGE_U8) &&
        (output->width > 0) &&
        (output->height > 0))
    {
        vx_rectangle_t rect = { 0, 0, output->width, output->height};
        vx_size stride = output->width;
        vx_size stride_y = sizeof(vx_coordinates2df_t) * (stride);
        vx_size size = stride * output->height;
        vx_coordinates2df_t *ptr_r = ct_calloc(size, sizeof(vx_coordinates2df_t));
        ASSERT_(return vx_false_e, ptr_r);
        vxCopyRemapPatch(map, &rect, stride_y, ptr_r, VX_TYPE_COORDINATES2DF, VX_READ_ONLY, VX_MEMORY_TYPE_HOST);

        uint32_t x, y;
        for (y = 0; y < output->height; y++) {
            for (x = 0; x < output->width; x++) {
                uint8_t* dst_data = CT_IMAGE_DATA_PTR_8U(output, x, y); (void)dst_data;
                {
                    vx_coordinates2df_t *coord_ptr = &(ptr_r[y * stride + x]);
                    if (0 != remap_check_pixel(input, output, x, y, coord_ptr->x, coord_ptr->y,
                                               interp_type, border, map, vx_false_e))
                    {
                        ct_free_mem(ptr_r);
                        return vx_false_e;
                    }
                }
            }
        }
        ct_free_mem(ptr_r);
        return vx_true_e;
    }
    else
    {
        return vx_false_e;
    }
}

static void remap_check(CT_Image input, CT_Image output, vx_enum interp_type, vx_border_t border, vx_remap map)
{
    ASSERT(input && output);
    ASSERT( (interp_type == VX_INTERPOLATION_NEAREST_NEIGHBOR) ||
            (interp_type == VX_INTERPOLATION_BILINEAR));

    ASSERT( (border.mode == VX_BORDER_UNDEFINED) ||
            (border.mode == VX_BORDER_CONSTANT) );

    remap_validate(input, output, interp_type, border, map);
    if (CT_HasFailure())
    {
        printf("=== INPUT ===\n");
        ct_dump_image_info(input);
        printf("=== OUTPUT ===\n");
        ct_dump_image_info(output);
    }
}

static void remap_check_param(CT_Image input, CT_Image output, vx_enum interp_type, vx_border_t border, vx_remap map)
{
    ASSERT(input && output);
    ASSERT( (interp_type == VX_INTERPOLATION_NEAREST_NEIGHBOR) ||
            (interp_type == VX_INTERPOLATION_BILINEAR));

    ASSERT( (border.mode == VX_BORDER_UNDEFINED) ||
            (border.mode == VX_BORDER_CONSTANT) ||
            (border.mode == VX_BORDER_REPLICATE) );
}

typedef struct {
    const char* testName;
    CT_Image(*generator)(const char* fileName, int width, int height);
    const char*      fileName;
    int width, height;
    vx_border_t border;
    vx_enum border_policy;
    vx_enum interp_type;
    int map_type;
} Arg;

#define ADD_VX_BORDERS_REMAP_FULL(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "/VX_BORDER_UNDEFINED", __VA_ARGS__, { VX_BORDER_UNDEFINED, {{ 0 }} })), \
    CT_EXPAND(nextmacro(testArgName "/VX_BORDER_CONSTANT=0", __VA_ARGS__, { VX_BORDER_CONSTANT, {{ 0 }} })), \
    CT_EXPAND(nextmacro(testArgName "/VX_BORDER_CONSTANT=1", __VA_ARGS__, { VX_BORDER_CONSTANT, {{ 1 }} })), \
    CT_EXPAND(nextmacro(testArgName "/VX_BORDER_CONSTANT=127", __VA_ARGS__, { VX_BORDER_CONSTANT, {{ 127 }} })), \
    CT_EXPAND(nextmacro(testArgName "/VX_BORDER_CONSTANT=255", __VA_ARGS__, { VX_BORDER_CONSTANT, {{ 255 }} }))

#define ADD_VX_BORDERS_REMAP_SMALL(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "/VX_BORDER_UNDEFINED", __VA_ARGS__, { VX_BORDER_UNDEFINED, {{ 0 }} })), \
    CT_EXPAND(nextmacro(testArgName "/VX_BORDER_CONSTANT=127", __VA_ARGS__, { VX_BORDER_CONSTANT, {{ 127 }} })), \
    CT_EXPAND(nextmacro(testArgName "/VX_BORDER_REPLICATE", __VA_ARGS__, { VX_BORDER_REPLICATE, {{ 0 }} }))

#define ADD_VX_BORDERS_POLICY(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "/DEFAULT_TO_UNDEFINED", __VA_ARGS__, VX_BORDER_POLICY_DEFAULT_TO_UNDEFINED)), \
    CT_EXPAND(nextmacro(testArgName "/RETURN_ERROR", __VA_ARGS__, VX_BORDER_POLICY_RETURN_ERROR))

#define ADD_VX_BORDERS_NO_POLICY(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "", __VA_ARGS__, (vx_enum)0))

#define ADD_VX_INTERP_TYPE_REMAP(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "/VX_INTERPOLATION_NEAREST_NEIGHBOR", __VA_ARGS__, VX_INTERPOLATION_NEAREST_NEIGHBOR)), \
    CT_EXPAND(nextmacro(testArgName "/VX_INTERPOLATION_BILINEAR", __VA_ARGS__, VX_INTERPOLATION_BILINEAR ))

#define ADD_VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "/VX_INTERPOLATION_NEAREST_NEIGHBOR", __VA_ARGS__, VX_INTERPOLATION_NEAREST_NEIGHBOR))

#define ADD_VX_INTERPOLATION_TYPE_BILINEAR(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "/VX_INTERPOLATION_BILINEAR", __VA_ARGS__, VX_INTERPOLATION_BILINEAR ))

#define ADD_VX_MAP_PARAM_REMAP_FULL(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "/VX_MAP_IDENT", __VA_ARGS__,        VX_MAP_IDENT)), \
    CT_EXPAND(nextmacro(testArgName "/VX_MAP_SCALE", __VA_ARGS__,        VX_MAP_SCALE)), \
    CT_EXPAND(nextmacro(testArgName "/VX_MAP_SCALE_ROTATE", __VA_ARGS__, VX_MAP_SCALE_ROTATE)), \
    CT_EXPAND(nextmacro(testArgName "/VX_MAP_RANDOM", __VA_ARGS__,       VX_MAP_RANDOM))

#define ADD_VX_MAP_PARAM_REMAP_SMALL(testArgName, nextmacro, ...) \
    CT_EXPAND(nextmacro(testArgName "/VX_MAP_IDENT", __VA_ARGS__,        VX_MAP_IDENT))

#define REMAP_PARAMETERS \
    CT_GENERATE_PARAMETERS("random", ADD_SIZE_SMALL_SET, ADD_VX_BORDERS_REMAP_FULL, ADD_VX_BORDERS_NO_POLICY, ADD_VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR, ADD_VX_MAP_PARAM_REMAP_FULL, ARG, remap_generate_random, NULL), \
    CT_GENERATE_PARAMETERS("lena", ADD_SIZE_SMALL_SET, ADD_VX_BORDERS_REMAP_FULL, ADD_VX_BORDERS_NO_POLICY, ADD_VX_INTERP_TYPE_REMAP, ADD_VX_MAP_PARAM_REMAP_FULL, ARG, remap_read_image_8u, "lena.bmp")

#define POLICY_PARAMETERS \
    CT_GENERATE_PARAMETERS("random", ADD_SIZE_SMALL_SET, ADD_VX_BORDERS_REMAP_SMALL, ADD_VX_BORDERS_POLICY, ADD_VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR, ADD_VX_MAP_PARAM_REMAP_SMALL, ARG, remap_generate_random, NULL)

TEST_WITH_ARG(Remap, testGraphProcessing, Arg,
    REMAP_PARAMETERS
)
{
    vx_context context = context_->vx_context_;
    vx_graph graph = 0;
    vx_node node = 0;
    vx_image input_image = 0, output_image = 0;
    vx_remap map = 0;

    CT_Image input = NULL, output = NULL;

    vx_border_t border = arg_->border;

    ASSERT_NO_FAILURE(input = arg_->generator(arg_->fileName, SRC_WIDTH, SRC_HEIGHT));
    ASSERT_NO_FAILURE(output = ct_allocate_image(arg_->width, arg_->height, VX_DF_IMAGE_U8));

    ASSERT_VX_OBJECT(input_image = ct_image_to_vx_image(input, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(output_image = ct_image_to_vx_image(output, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(map = remap_generate_map(context, input->width, input->height, arg_->width, arg_->height, arg_->map_type), VX_TYPE_REMAP);

    ASSERT_VX_OBJECT(graph = vxCreateGraph(context), VX_TYPE_GRAPH);

    ASSERT_VX_OBJECT(node = vxRemapNode(graph, input_image, map, arg_->interp_type, output_image), VX_TYPE_NODE);

    VX_CALL(vxSetNodeAttribute(node, VX_NODE_BORDER, &border, sizeof(border)));

    VX_CALL(vxVerifyGraph(graph));
    VX_CALL(vxProcessGraph(graph));

    ASSERT_NO_FAILURE(output = ct_image_from_vx_image(output_image));
    ASSERT_NO_FAILURE(remap_check(input, output, arg_->interp_type, arg_->border, map));

    VX_CALL(vxReleaseNode(&node));
    VX_CALL(vxReleaseGraph(&graph));
    VX_CALL(vxReleaseRemap(&map));
    VX_CALL(vxReleaseImage(&output_image));
    VX_CALL(vxReleaseImage(&input_image));

    ASSERT(node == 0);
    ASSERT(graph == 0);
    ASSERT(map == 0);
    ASSERT(output_image == 0);
    ASSERT(input_image == 0);
}

TEST_WITH_ARG(Remap, testImmediateProcessing, Arg,
    REMAP_PARAMETERS
)
{
    vx_context context = context_->vx_context_;
    vx_image input_image = 0, output_image = 0;
    vx_remap map = 0;

    CT_Image input = NULL, output = NULL;

    vx_border_t border = arg_->border;

    ASSERT_NO_FAILURE(input = arg_->generator(arg_->fileName, SRC_WIDTH, SRC_HEIGHT));
    ASSERT_NO_FAILURE(output = ct_allocate_image(arg_->width, arg_->height, VX_DF_IMAGE_U8));

    ASSERT_VX_OBJECT(input_image = ct_image_to_vx_image(input, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(output_image = ct_image_to_vx_image(output, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(map = remap_generate_map(context, input->width, input->height, arg_->width, arg_->height, arg_->map_type), VX_TYPE_REMAP);

    VX_CALL(vxSetContextAttribute(context, VX_CONTEXT_IMMEDIATE_BORDER, &border, sizeof(border)));

    VX_CALL(vxuRemap(context, input_image, map, arg_->interp_type, output_image));

    ASSERT_NO_FAILURE(output = ct_image_from_vx_image(output_image));

    ASSERT_NO_FAILURE(remap_check(input, output, arg_->interp_type, arg_->border, map));

    VX_CALL(vxReleaseRemap(&map));
    VX_CALL(vxReleaseImage(&output_image));
    VX_CALL(vxReleaseImage(&input_image));

    ASSERT(map == 0);
    ASSERT(output_image == 0);
    ASSERT(input_image == 0);
}

TEST_WITH_ARG(Remap, testImmediatePolicy, Arg,
    POLICY_PARAMETERS
)
{
    vx_context context = context_->vx_context_;
    vx_image input_image = 0, output_image = 0;
    vx_remap map = 0;

    CT_Image input = NULL, output = NULL;

    vx_border_t border = arg_->border;
    vx_enum border_policy = arg_->border_policy;
    vx_enum actual_policy = border_policy;

    vx_status expected_status = VX_SUCCESS;
    vx_status status = VX_SUCCESS;
    vx_border_t checked_border;

    ASSERT_NO_FAILURE(input = arg_->generator(arg_->fileName, SRC_WIDTH, SRC_HEIGHT));
    ASSERT_NO_FAILURE(output = ct_allocate_image(arg_->width, arg_->height, VX_DF_IMAGE_U8));

    ASSERT_VX_OBJECT(input_image = ct_image_to_vx_image(input, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(output_image = ct_image_to_vx_image(output, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(map = remap_generate_map(context, input->width, input->height, arg_->width, arg_->height, arg_->map_type), VX_TYPE_REMAP);

    VX_CALL(vxSetContextAttribute(context, VX_CONTEXT_IMMEDIATE_BORDER, &border, sizeof(border)));

    /* Now VX_CONTEXT_IMMEDIATE_BORDER_POLICY is readonly */
    /* VX_CALL(vxSetContextAttribute(context, VX_CONTEXT_IMMEDIATE_BORDER_POLICY, &border_policy, sizeof(border_policy))); */
    VX_CALL(vxQueryContext(context, VX_CONTEXT_IMMEDIATE_BORDER_POLICY, &actual_policy, sizeof(actual_policy)));
    border_policy = actual_policy;

    status = vxuRemap(context, input_image, map, arg_->interp_type, output_image);

    checked_border = border;
    if (border.mode == VX_BORDER_REPLICATE)
    {
        checked_border.mode = VX_BORDER_UNDEFINED;
        switch (border_policy)
        {
        case VX_BORDER_POLICY_DEFAULT_TO_UNDEFINED:
            expected_status = VX_SUCCESS;
            break;
        case VX_BORDER_POLICY_RETURN_ERROR:
            expected_status = VX_ERROR_NOT_SUPPORTED;
            break;
        default:
            expected_status = VX_FAILURE;
            break;
        }
    }
    else
    {
        expected_status = VX_SUCCESS;
    }
        
    if (status == VX_SUCCESS)
    {
        ASSERT_NO_FAILURE(output = ct_image_from_vx_image(output_image));

        if ((border.mode == VX_BORDER_REPLICATE) && (checked_border.mode != border.mode))
        {
            /* In case of VX_BORDER_POLICY_DEFAULT_TO_UNDEFINED vxu function can return VX_SUCCESS in both cases
             * a) it supports VX_BORDER_REPLICATE, and VX_BORDER_REPLICATE actually used;
             * b) it doesn't support VX_BORDER_REPLICATE, and VX_BORDER_UNDEFINED actually used instead.
             * Using API we can't distingish the cases, so we must check both
             */
            vx_bool equal = vx_false_e;
            ASSERT_NO_FAILURE(remap_check_param(input, output, arg_->interp_type, border, map));
            equal = remap_is_equal(input, output, arg_->interp_type, border, map);
            if (equal == vx_false_e)
                equal = remap_is_equal(input, output, arg_->interp_type, checked_border, map);
            if (equal == vx_false_e)
                ASSERT_NO_FAILURE(remap_check(input, output, arg_->interp_type, border, map));
        }
        else
        {
            ASSERT_NO_FAILURE(remap_check(input, output, arg_->interp_type, border, map));
        }
    }
    else
    {
        ASSERT_EQ_VX_STATUS(expected_status, status);
    }

    VX_CALL(vxReleaseRemap(&map));
    VX_CALL(vxReleaseImage(&output_image));
    VX_CALL(vxReleaseImage(&input_image));

    ASSERT(map == 0);
    ASSERT(output_image == 0);
    ASSERT(input_image == 0);
}

TEST(Remap, testNodeVirtualCreation)
{
    vx_context context = context_->vx_context_;
    vx_uint32 src_width;
    vx_uint32 src_height;
    vx_uint32 dst_width;
    vx_uint32 dst_height;
    vx_remap map = 0;
    vx_graph graph = 0;

    src_width = 16;
    src_height = 32;
    dst_width = 128;
    dst_height = 64;

    graph = vxCreateGraph(context);
    ASSERT_VX_OBJECT(graph, VX_TYPE_GRAPH);

    map = vxCreateVirtualRemap(graph, src_width, src_height, dst_width, dst_height);
    ASSERT_VX_OBJECT(map, VX_TYPE_REMAP);

    VX_CALL(vxReleaseGraph(&graph));
    VX_CALL(vxReleaseRemap(&map));

    ASSERT(graph == 0);
    ASSERT(map == 0);
}

/* ******************vxCopyRemapPatch tests*****************/
TESTCASE(vxCopyRemapPatch, CT_VXContext, ct_setup_vx_context, 0)

TEST(vxCopyRemapPatch, testCopyRandomReamp)
{
    vx_context context = context_->vx_context_;
    vx_image input_image = 0, output_image = 0;
    vx_remap map = 0;

    CT_Image input = NULL, output = NULL;

    vx_border_t border;
    border.mode = VX_BORDER_UNDEFINED;

    vx_size src_width = 16;
    vx_size src_height = 32;
    vx_size dst_width = 128;
    vx_size dst_height = 64;

    //first: Create 'input' and 'output' images no need to have them big .
    //       Initialize 'input' with some values(preferably different for each pixel).
    //       Initialize 'output' with 0.
    ASSERT_NO_FAILURE(input = remap_generate_random("remap", src_width, src_height));
    ASSERT_NO_FAILURE(output = ct_allocate_image(dst_width, dst_height, VX_DF_IMAGE_U8));

    //second: Create a remap object and initialize the remap object with some values(copy 'WRITE'),
    //        preferably different for each cooridinate
    //        Read the remap object (copy 'READ') into a buffer different form the one use for the WRITE, and check that it's OK
    //        (if not different, the READ could do nothing and have correct result)
    ASSERT_VX_OBJECT(input_image = ct_image_to_vx_image(input, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(output_image = ct_image_to_vx_image(output, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(map = remap_generate_map(context, input->width, input->height, output->width, output->height, VX_MAP_IDENT), VX_TYPE_REMAP);
    VX_CALL(vxSetContextAttribute(context, VX_CONTEXT_IMMEDIATE_BORDER, &border, sizeof(border)));

    //third: Execute a graph with a 'remap' node
    VX_CALL(vxuRemap(context, input_image, map, VX_INTERPOLATION_NEAREST_NEIGHBOR, output_image));
    ASSERT_NO_FAILURE(output = ct_image_from_vx_image(output_image));

    //fourth: Check that the content of the 'output' image is consistent with the input image
    //        and the values copied into the remap object
    ASSERT_NO_FAILURE(remap_check(input, output, VX_INTERPOLATION_NEAREST_NEIGHBOR, border, map));

    VX_CALL(vxReleaseRemap(&map));
    VX_CALL(vxReleaseImage(&output_image));
    VX_CALL(vxReleaseImage(&input_image));

    ASSERT(map == 0);
    ASSERT(output_image == 0);
    ASSERT(input_image == 0);
}

/* *****************vxMapRemapPatch tests*******************************/
TESTCASE(vxMapRemapPatch, CT_VXContext, ct_setup_vx_context, 0)

static void remap_validate_for_map(CT_Image input, CT_Image output, vx_enum interp_type, vx_border_t border, vx_remap map)
{
    vx_uint32 err_count = 0;

    ASSERT(output != NULL);
    ASSERT(output->format == VX_DF_IMAGE_U8);
    ASSERT(output->width > 0);
    ASSERT(output->height > 0);
    {
        vx_rectangle_t rect = { 0, 0, output->width, output->height};

        vx_size map_stride_y = 0;
        vx_coordinates2df_t *ptr_r = 0;
        vx_map_id map_id;
        vxMapRemapPatch(map, &rect, &map_id, &map_stride_y, (void **)&ptr_r, VX_TYPE_COORDINATES2DF, VX_READ_ONLY, VX_MEMORY_TYPE_HOST);

        uint32_t x, y;
        for (y = 0; y < output->height; y++) {
            for (x = 0; x < output->width; x++) {
                uint8_t* dst_data = CT_IMAGE_DATA_PTR_8U(output, x, y); (void)dst_data;
                {
                    vx_coordinates2df_t *coord_ptr = (vx_coordinates2df_t *)(
                                ((vx_uint8 *)ptr_r) +
                                x*sizeof(vx_coordinates2df_t) +
                                y * map_stride_y /* map in bytes */
                                );
                    ASSERT_NO_FAILURE(err_count += remap_check_pixel(input, output, x, y, coord_ptr->x, coord_ptr->y,
                                                                     interp_type, border, map, vx_true_e));
                }
            }
        }

        vxUnmapRemapPatch(map, map_id);
    }

    if (10 * err_count > output->width * output->height)
        CT_FAIL_(return, "Check failed for %d pixels", err_count);
}

TEST(vxMapRemapPatch, testMapRandomRemap)
{
    vx_context context = context_->vx_context_;
    vx_image input_image = 0, output_image = 0;
    vx_remap map = 0;

    CT_Image input = NULL, output = NULL;

    vx_border_t border;
    border.mode = VX_BORDER_UNDEFINED;

    vx_size src_width = 16;
    vx_size src_height = 32;
    vx_size dst_width = 128;
    vx_size dst_height = 64;

    //first: Create 'input' and 'output' images no need to have them big .
    //       Initialize 'input' with some values(preferably different for each pixel).
    //       Initialize 'output' with 0.
    ASSERT_NO_FAILURE(input = remap_generate_random("remap", src_width, src_height));
    ASSERT_NO_FAILURE(output = ct_allocate_image(dst_width, dst_height, VX_DF_IMAGE_U8));

    //second: Create a remap object and initialize the remap object with some values(copy 'WRITE'),
    //        preferably different for each cooridinate
    //        Read the remap object (copy 'READ') into a buffer different form the one use for the WRITE, and check that it's OK
    //        (if not different, the READ could do nothing and have correct result)
    ASSERT_VX_OBJECT(input_image = ct_image_to_vx_image(input, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(output_image = ct_image_to_vx_image(output, context), VX_TYPE_IMAGE);
    ASSERT_VX_OBJECT(map = remap_generate_map(context, input->width, input->height, output->width, output->height, VX_MAP_IDENT), VX_TYPE_REMAP);
    VX_CALL(vxSetContextAttribute(context, VX_CONTEXT_IMMEDIATE_BORDER, &border, sizeof(border)));

    //third: Execute a graph with a 'remap' node
    VX_CALL(vxuRemap(context, input_image, map, VX_INTERPOLATION_NEAREST_NEIGHBOR, output_image));
    ASSERT_NO_FAILURE(output = ct_image_from_vx_image(output_image));

    //fourth: Check that the content of the 'output' image is consistent with the input image
    //        and the values copied into the remap object
    ASSERT_NO_FAILURE(remap_validate_for_map(input, output, VX_INTERPOLATION_NEAREST_NEIGHBOR, border, map));

    VX_CALL(vxReleaseRemap(&map));
    VX_CALL(vxReleaseImage(&output_image));
    VX_CALL(vxReleaseImage(&input_image));

    ASSERT(map == 0);
    ASSERT(output_image == 0);
    ASSERT(input_image == 0);
}

TESTCASE_TESTS(Remap,
        testNodeCreation,
        testGraphProcessing,
        testImmediateProcessing,
        testImmediatePolicy,
        testNodeVirtualCreation
)

TESTCASE_TESTS(vxCopyRemapPatch, testCopyRandomReamp)
TESTCASE_TESTS(vxMapRemapPatch, testMapRandomRemap)

